package com.example.steriousfx.utils;

import cn.yeziji.http.tools.MRequest;
import com.example.steriousfx.utils.entity.UrlParsingResult;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.protocol.HTTP;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 下载资源工具类
 *
 * @author gzkemays
 * @since 2021/10/17 14:41
 */
@Slf4j
@AllArgsConstructor
public class DownloadSourceUtils {
  private static final String LOCATION_SCRIPT_PATTERN = "window.location.href";
  private static final Pattern PASSWORD_PATTERN = Pattern.compile("[\\d]");
  private static final Pattern LOCATION_COLON_PATTERN = Pattern.compile("\"(.*?)\"");
  private static final int MAX_RELOAD = 5;
  private static final AtomicInteger RELOAD = new AtomicInteger(0);

  /**
   * 获取重定向地址
   *
   * @param url 原地址
   * @return 返回的最终地址
   * @throws IOException IO 异常
   */
  public String getLocationUrl(String url) throws IOException {
    HttpGet noDataGet = MRequest.getNoDataGet(url);
    noDataGet.setConfig(MRequest.getRequestConfig());
    Document document = Jsoup.parse(MRequest.getHtmlTagWithGet(noDataGet, "UTF-8"));
    return getLocationUrl(document);
  }

  /**
   * 截取重定向过去的地址 location script
   *
   * @param document 读取的 Document 文章资料
   * @return 返回的地址
   */
  private String getLocationUrl(Document document) {
    String html = document.html().trim();
    if (html.contains(LOCATION_SCRIPT_PATTERN)) {
      Matcher matcher = LOCATION_COLON_PATTERN.matcher(document.html());
      boolean find = matcher.find();
      if (find) {
        String group = matcher.group();
        return group.replace("\"", "");
      }
    }
    return null;
  }

  /**
   * 返回 URLS 列表
   *
   * @param url 实际获取资源的页面地址
   * @param password 访问网页的密码
   * @return {@link UrlParsingResult} 解析实体
   */
  public UrlParsingResult getUrlsList(String url, String password) throws IOException {
    ArrayList<String> urls = new ArrayList<>();
    UrlParsingResult result = new UrlParsingResult();
    HttpGet noDataGet = MRequest.getNoDataGet(url);
    noDataGet.setConfig(MRequest.getRequestConfig());
    noDataGet.setHeader("Cookie", "pwd=" + password);
    noDataGet.setHeader(
        HTTP.USER_AGENT,
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36 Edg/94.0.992.50");
    Document document = Jsoup.parse(MRequest.getHtmlTagWithGet(noDataGet, "UTF-8"));
    try {
      Elements article = document.getElementsByTag("article");
      // 主体文章内的图片
      Elements contents = article.first().getElementsByTag("img");
      // 去广告
      List<Element> elementList = contents.subList(0, contents.size() - 2);
      // 正文操作（获取正文标题以及数据列表）
      String title = document.getElementsByClass("note-title").text();
      for (Element element : elementList) {
        String src = element.attr("src");
        // 图片由于网络问题导致的图片读取不成功则需要整个页面重新捕获。
        if (src.contains("data:image")) {
          log.warn("网络稍差,重新获取.");
          getUrlsList(url, password);
        }
        urls.add(src);
      }
      result.setUrls(urls);
      result.setTitle(title);
      return result;
    } catch (Exception e) {
      e.printStackTrace();
      System.out.println(e.getCause() + "***** 重新获取地址");
      RELOAD.getAndIncrement();
      if (RELOAD.get() < MAX_RELOAD) {
        getUrlsList(url, password);
      }
      if (RELOAD.get() >= MAX_RELOAD) {
        return getImagesResultUrls(url);
      }
    }
    return null;
  }

  public UrlParsingResult getImagesResultUrls(String url) {
    WebClient browser = new WebClient();
    //    browser.getOptions().setCssEnabled(false);
    //    browser.getOptions().setJavaScriptEnabled(true);
    //    browser.getOptions().setThrowExceptionOnScriptError(false);
    try {
      HtmlPage htmlPage = browser.getPage(url);
      // 设置等待js的加载时间
      browser.waitForBackgroundJavaScript(3000);
      Document doc = Jsoup.parse(htmlPage.asXml());
      System.out.println(doc);
      RELOAD.set(0);
    } catch (IOException e) {
      e.printStackTrace();
    }
    return null;
  }

  /** 获取文章密码 */
  public String getUrlsPassword(String url) {
    String password = "";
    try {
      HttpGet noDataGet = MRequest.getNoDataGet(url);
      noDataGet.setConfig(MRequest.getRequestConfig());
      Document html = Jsoup.parse(MRequest.getHtmlTagWithGet(noDataGet, "UTF-8"));
      Elements metas = html.getElementsByTag("meta");
      // 遍历 meta 找到密码数据
      for (Element meta : metas) {
        String content = meta.attr("content");
        if (content.contains("密码")) {
          Matcher matcher = PASSWORD_PATTERN.matcher(content);
          StringBuilder sb = new StringBuilder();
          while (matcher.find()) {
            sb.append(matcher.group());
          }
          // 查找到密码后直接跳出
          password = sb.toString();
          break;
        }
      }
    } catch (Exception e) {
      System.out.println(e.getMessage() + "***** 重新获取密码");
      return getUrlsPassword(url);
    }
    return password;
  }

  /**
   * 已知地址和密码进行打包
   *
   * @param url 地址
   * @param password 密码
   */
  public void packageSourceZip(String url, String password) {
    try {
      UrlParsingResult urlsList = getUrlsList(url, password);
      packageFile(urlsList);

    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * 打包资源为 zip
   *
   * @param url 真实地址
   */
  public void packageSourceZip(String url) {
    try {
      String locationUrl = getLocationUrl(url);
      String urlsPassword = getUrlsPassword(locationUrl);
      UrlParsingResult urlsList = getUrlsList(locationUrl, urlsPassword);
      packageFile(urlsList);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * 打包文件
   *
   * @param result {@link UrlParsingResult} 实体类数据
   */
  public void packageFile(UrlParsingResult result) {
    System.out.println(result);
    String title = result.getTitle();
    FileOutputStream fos;
    ZipOutputStream tempZos = null;
    try {
      // 指定文件
      String downloadPath = "D:\\TempSave\\图片存根\\QQWEB";
      File file = new File(downloadPath);
      if (!file.exists()) {
        file.mkdirs();
      }
      File zipFile = new File(downloadPath + File.separator + title + ".zip");
      if (!zipFile.exists()) {
        zipFile.createNewFile();
        log.info("开始打包...");
        // 将数据库中 urls 转换为数组数据
        List<String> urls = result.getUrls();
        // 读取文件输出流
        fos = new FileOutputStream(zipFile);
        tempZos = new ZipOutputStream(fos);
        for (int i = 0; i < urls.size(); i++) {
          URL uri = new URL(urls.get(i));
          tempZos.putNextEntry(new ZipEntry(title + "-" + i + result.getSuffix()));
          // 此处设置了超时时间，如果超时时间内未访问，则跳过重新创建链接。进行重新访问……
          // 访问时间为 30 秒 (3M ms),具体情况要视情况而定.
          URLConnection urlConnection = uri.openConnection();
          urlConnection.setConnectTimeout(30000);
          urlConnection.setReadTimeout(30000);
          try {
            InputStream fis = urlConnection.getInputStream();
            byte[] buffer = new byte[1024];
            int r;
            while ((r = fis.read(buffer)) != -1) {
              tempZos.write(buffer, 0, r);
            }
          } catch (SocketTimeoutException e) {
            e.printStackTrace();
            log.warn(uri.getPath() + "：访问超时，重新建立当前链接。");
          } catch (IOException e2) {
            e2.printStackTrace();
            log.warn(uri.getPath() + "：ZIP 添加失败，进行跨越添加。");
            //            i++;
          }
        }
        log.info("打包资源完毕:{}", zipFile.getName());
      }
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (Objects.nonNull(tempZos)) {
        try {
          tempZos.flush();
          tempZos.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }

  private void downloadVideo(String url) {}
}
